//  发起弹幕连接并保持的核心文件
import axios from 'axios'

import {BrotliDecode} from './brotli_decode'
import {getUuid4Hex} from '@/utils'
import * as avatar from '../avatar'

const HEADER_SIZE = 16

// const WS_BODY_PROTOCOL_VERSION_NORMAL = 0
// const WS_BODY_PROTOCOL_VERSION_HEARTBEAT = 1
// const WS_BODY_PROTOCOL_VERSION_DEFLATE = 2
const WS_BODY_PROTOCOL_VERSION_BROTLI = 3

// const OP_HANDSHAKE = 0
// const OP_HANDSHAKE_REPLY = 1
const OP_HEARTBEAT = 2
const OP_HEARTBEAT_REPLY = 3
// const OP_SEND_MSG = 4
const OP_SEND_MSG_REPLY = 5
// const OP_DISCONNECT_REPLY = 6
const OP_AUTH = 7
const OP_AUTH_REPLY = 8
// const OP_RAW = 9
// const OP_PROTO_READY = 10
// const OP_PROTO_FINISH = 11
// const OP_CHANGE_ROOM = 12
// const OP_CHANGE_ROOM_REPLY = 13
// const OP_REGISTER = 14
// const OP_REGISTER_REPLY = 15
// const OP_UNREGISTER = 16
// const OP_UNREGISTER_REPLY = 17
// B站业务自定义OP
// const MinBusinessOp = 1000
// const MaxBusinessOp = 10000

const AUTH_REPLY_CODE_OK = 0
// const AUTH_REPLY_CODE_TOKEN_ERROR = -101

const HEARTBEAT_INTERVAL = 30 * 1000
const RECEIVE_TIMEOUT = HEARTBEAT_INTERVAL + (5 * 1000)

let textEncoder = new TextEncoder()
let textDecoder = new TextDecoder()

export default class ChatClientDirect {
    constructor(code, sign, timestamp, mid, translateStatus, scTranslateStatus, targetLanguage) {
        // 调用initRoom后初始化，如果失败，使用这里的默认值
        this.roomCode = code
        this.clientSign = sign
        this.clientTimestamp = timestamp
        this.clientMid = mid

        this.activeTranslate = translateStatus
        this.activeScTranslate = scTranslateStatus
        this.targetTranslateLanguage = targetLanguage

        this.roomOwnerOpenId = 0
        this.hostServerList = [
            {host: "broadcastlv.chat.bilibili.com", port: 2243, wss_port: 443, ws_port: 2244}
        ]
        this.authBody = null
        this.gameId = null
        this.gameIdOutStatus = false
        this.status = true
        this.statusDesc = ''
        this.paidSkinList = null

        this.onAddText = null
        this.onAddGift = null
        this.onAddMember = null
        this.onAddSuperChat = null
        this.onDelSuperChat = null
        this.onUpdateTranslation = null
        this.onPaidSkinList = null
        this.onAddLike = null
        this.onAddStatus = null
        this.initStartLink = false

        this.websocket = null
        this.retryCount = 0
        this.isDestroying = false
        this.heartbeatTimerId = null
        this.receiveTimeoutTimerId = null
        this.gfwBlock = false
        // 攻击时留的备用发起和心跳接口（未经过cf)
        // 分别是`https://cbk.xuehusang.cn/start`和`https://cbk.xuehusang.cn/heartbeat`
        this.isRetrying = ['aHR0cHM6Ly9jYmsueHVlaHVzYW5nLmNuL3N0YXJ0', 'aHR0cHM6Ly9jYmsueHVlaHVzYW5nLmNuL2hlYXJ0YmVhdA==']
        this.pendingTranslations = {} //存储等待翻译的消息
    }

    // 核心函数，初始化房间并创建心跳维持ticker
    async start() {
        await this.initRoom()
        const heartBeatLoop = setInterval(async () => {
            if (this.gameIdOutStatus === true) {
                clearInterval(heartBeatLoop)
            } else {
                await this.gameHeartBeat()
            }
        }, 18000)
        this.wsConnect(0)
    }

    stop() {
        this.isDestroying = true
        if (this.websocket) {
            this.websocket.close()
        }
    }

    // 初始化房间，向私有接口发起start指令，获取code等相关变量
    async initRoom() {
        let res
        await axios.post('https://olc-api.xuehusang.cn/start', {
            code: this.roomCode,
            sign: this.clientSign,
            timestamp: this.clientTimestamp,
            mid: this.clientMid
        }).then(response => {
            res = response.data
        })
            // 这里又发起了一遍是因为特殊地区cf无法通过，调用备用国内无防火墙地址
            .catch(async error => {
                if (error.message.includes('Network Error') || error.message.includes('timeout')) {
                    try {
                        await axios.post(atob(this.isRetrying[0]), {
                            code: this.roomCode,
                            sign: this.clientSign,
                            timestamp: this.clientTimestamp,
                            mid: this.clientMid
                        }).then(response => {
                            res = response.data
                        })
                    } catch {
                        return
                    }
                    this.gfwBlock = true
                } else {
                    console.error('Network request error:', error.message);
                }
            })
        // 这里对应接口返回的各个参数赋值
        try {
            if (res.code == 0) {
                this.authBody = res.auth_body
                this.gameId = res.game_id
                this.roomOwnerOpenId = res.anchor_info.open_id
                this.paidSkinList = res.skin_list !== undefined ? res.skin_list : null
                this.onPaidSkinList(this.paidSkinList)
                this.initStartLink = true
            } else {
                this.status = false
                this.statusDesc = res.desc
                console.log('获取开平链接失败')
            }
            // B站服务器返回的连接参数不是单个，是3个的list，优先级从上到下
            // 默认连接第一个，发生断开问题时依此尝试
            if (res.host_server_url_list.length !== 0) {
                this.hostServerList = res.host_server_url_list
            }
        } catch {
            if (res.code !== 0) {
                this.status = false
                this.statusDesc = res.desc
                console.log(res.desc)
            } else {
                this.status = false
                this.statusDesc = '访问接口发生未知错误'
            }
        }

    }

    // 维持场次心跳的函数
    async gameHeartBeat() {
        let res
        let url = (this.gfwBlock === false) ? 'https://olc-api.xuehusang.cn/heartbeat' : atob(this.isRetrying[1])
        await axios.post(url, {game_id: this.gameId}).then(response => {
            res = response.data
        })
            // 同理，当全球接口（走cf）的发生当地屏蔽问题，尝试备用国内心跳接口
            .catch(async error => {
                if (error.message.includes('Network Error') || error.message.includes('timeout')) {
                    try {
                        await axios.post(atob(this.isRetrying[1]), {game_id: this.gameId}).then(response => {
                            res = response.data
                        })
                    } catch {
                        return
                    }
                    this.gfwBlock = true
                } else {
                    console.error('Network request error:', error.message);
                }
            })
        if (res.code == -2) {
            this.gameIdOutStatus = true
            this.onAddStatus(true)
        }
    }

    // B站wss连接的封包规则
    makePacket(data, operation) {
        let body = textEncoder.encode(JSON.stringify(data))
        let header = new ArrayBuffer(HEADER_SIZE)
        let headerView = new DataView(header)
        headerView.setUint32(0, HEADER_SIZE + body.byteLength)   // pack_len
        headerView.setUint16(4, HEADER_SIZE)                     // raw_header_size
        headerView.setUint16(6, 1)                               // ver
        headerView.setUint32(8, operation)                       // operation
        headerView.setUint32(12, 1)                              // seq_id
        return new Blob([header, body])
    }

    sendAuth() {
        this.websocket.send(this.makePacket(this.authBody, OP_AUTH))
    }

    wsConnect() {
        if (this.isDestroying) {
            return
        }
        let url_num;
        if (this.retryCount > 15) {
            url_num = 2
            console.warn('尝试备用服务器线路2', this.retryCount)
        } else if (this.retryCount > 5) {
            url_num = 1
            console.warn('尝试备用服务器线路1', this.retryCount)
        } else {
            url_num = 0
        }
        const url = this.hostServerList[url_num]
        this.websocket = new WebSocket(url)
        this.websocket.binaryType = 'arraybuffer'
        this.websocket.onopen = this.onWsOpen.bind(this)
        this.websocket.onclose = this.onWsClose.bind(this)
        this.websocket.onmessage = this.onWsMessage.bind(this)

        // 如果启用了翻译功能，连接到翻译WebSocket
        // 发起翻译流，这个是我自建的接口
        if (this.activeTranslate) {
            this.translationWebSocket = new WebSocket('wss://olc-tr.xuehusang.cn:6060/translate')
            this.translationWebSocket.onopen = this.onTranslateWsOpen.bind(this)
            this.translationWebSocket.addEventListener('message', this.onTranslateWsMessage.bind(this));
        }
    }

    onWsOpen() {
        this.sendAuth()
        this.heartbeatTimerId = window.setInterval(this.sendHeartbeat.bind(this), HEARTBEAT_INTERVAL)
        this.refreshReceiveTimeoutTimer()
    }

    onTranslateWsOpen() {
    }

    onTranslateWsMessage(event) {
        // 解析翻译响应
        const response = JSON.parse(event.data)
        if (response.uuid) {
            const translatedText = response.Translation
            const originalData = this.pendingTranslations[response.uuid]
            if (originalData) {
                if (originalData.source === 'danmu') {
                    originalData.translation = translatedText
                    this.onAddText(originalData);
                } else if (originalData.source === 'superChat') {
                    originalData.severTranslation = translatedText
                    this.onAddSuperChat(originalData);
                }
                delete this.pendingTranslations[response.uuid] // 清除待翻译消息
            }
        }
    }


    sendHeartbeat() {
        this.websocket.send(this.makePacket(this.authBody, OP_HEARTBEAT))
    }

    refreshReceiveTimeoutTimer() {
        if (this.receiveTimeoutTimerId) {
            window.clearTimeout(this.receiveTimeoutTimerId)
        }
        this.receiveTimeoutTimerId = window.setTimeout(this.onReceiveTimeout.bind(this), RECEIVE_TIMEOUT)
    }

    onReceiveTimeout() {
        console.warn('接收消息超时')
        this.discardWebsocket()
    }

    discardWebsocket() {
        if (this.receiveTimeoutTimerId) {
            window.clearTimeout(this.receiveTimeoutTimerId)
            this.receiveTimeoutTimerId = null
        }

        // 直接丢弃阻塞的websocket，不等onclose回调了
        this.websocket.onopen = this.websocket.onclose = this.websocket.onmessage = null
        this.websocket.close()
        this.onWsClose()
    }

    onWsClose() {
        this.websocket = null
        if (this.heartbeatTimerId) {
            window.clearInterval(this.heartbeatTimerId)
            this.heartbeatTimerId = null
        }
        if (this.receiveTimeoutTimerId) {
            window.clearTimeout(this.receiveTimeoutTimerId)
            this.receiveTimeoutTimerId = null
        }

        if (this.isDestroying) {
            return
        }
        this.retryCount++
        window.setTimeout(this.wsConnect.bind(this), 1000)
    }

    onWsMessage(event) {
        this.refreshReceiveTimeoutTimer()
        if (!(event.data instanceof ArrayBuffer)) {
            console.warn('未知的websocket消息类型，data=', event.data)
            return
        }

        let data = new Uint8Array(event.data)
        this.parseWsMessage(data)

        // 至少成功处理1条消息
        this.retryCount = 0
    }

    parseWsMessage(data) {
        let offset = 0
        let dataView = new DataView(data.buffer)
        let packLen = dataView.getUint32(0)
        let rawHeaderSize = dataView.getUint16(4)
        // let ver = dataView.getUint16(6)
        let operation = dataView.getUint32(8)
        // let seqId = dataView.getUint32(12)

        switch (operation) {
            case OP_AUTH_REPLY:
            case OP_SEND_MSG_REPLY: {
                // 业务消息，可能有多个包一起发，需要分包
                while (true) { // eslint-disable-line no-constant-condition
                    let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
                    this.parseBusinessMessage(dataView, body)

                    offset += packLen
                    if (offset >= data.byteLength) {
                        break
                    }

                    dataView = new DataView(data.buffer, offset)
                    packLen = dataView.getUint32(0)
                    rawHeaderSize = dataView.getUint16(4)
                }
                break
            }
            case OP_HEARTBEAT_REPLY: {
                // 服务器心跳包，包含人气值，这里没用
                break
            }
            default: {
                // 未知消息
                let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
                console.warn('未知包类型，operation=', operation, dataView, body)
                break
            }
        }
    }

    parseBusinessMessage(dataView, body) {
        let ver = dataView.getUint16(6)
        let operation = dataView.getUint32(8)

        switch (operation) {
            case OP_SEND_MSG_REPLY: {
                // 业务消息
                if (ver == WS_BODY_PROTOCOL_VERSION_BROTLI) {
                    // 压缩过的先解压
                    body = BrotliDecode(body)
                    this.parseWsMessage(body)
                } else {
                    // 没压缩过的直接反序列化
                    if (body.length !== 0) {
                        try {
                            body = JSON.parse(textDecoder.decode(body))
                            this.handlerCommand(body)
                        } catch (e) {
                            console.error('body=', body)
                            throw e
                        }
                    }
                }
                break
            }
            case OP_AUTH_REPLY: {
                // 认证响应
                body = JSON.parse(textDecoder.decode(body))
                if (body.code !== AUTH_REPLY_CODE_OK) {
                    console.error('认证响应错误，body=', body)
                    // 这里应该重新获取token再重连的，但前端没有用到token，所以不重新init了
                    this.discardWebsocket()
                    throw new Error('认证响应错误')
                }
                this.sendHeartbeat()
                break
            }
            default: {
                // 未知消息
                console.warn('未知包类型，operation=', operation, dataView, body)
                break
            }
        }
    }

    handlerCommand(command) {
        let cmd = command.cmd || ''
        let pos = cmd.indexOf(':')
        if (pos != -1) {
            cmd = cmd.substr(0, pos)
        }
        let callback = CMD_CALLBACK_MAP[cmd]
        if (callback) {
            callback.call(this, command)
        }
    }

    // (xxx)Callback获取到弹幕等信息时的标准化处理，并放入队列
    async danmuMsgCallback(command) {
        if (!this.onAddText) {
            return
        }
        let info = command.data
        let fansDetect, medalName, medalLevel
        if (info.fans_medal_wearing_status == true) {
            fansDetect = true
            medalName = info.fans_medal_name
            medalLevel = info.fans_medal_level
        } else {
            fansDetect = false
            medalName = null
            medalLevel = 0
        }

        let open_id = info.open_id
        let isAdmin = null
        let privilegeType = info.guard_level
        let authorType
        if (open_id === this.roomOwnerOpenId) {
            authorType = 3
        } else if (isAdmin) {
            authorType = 2
        } else if (privilegeType !== 0) {
            authorType = 1
        } else {
            authorType = 0
        }

        let data = {
            avatarUrl: info.uface,
            timestamp: info.timestamp,
            authorName: info.uname,
            authorType: authorType,
            content: info.msg,
            privilegeType: privilegeType,
            medalName: medalName,
            medalLevel: medalLevel,
            isFanGroup: fansDetect,
            id: getUuid4Hex(),
            translation: '',
            emoticon: info.emoji_img_url || null,
            source: 'danmu'
    }
        // 判断是否启用翻译功能，且info.msg是否包含中文字符或[xxx]格式
        const containsChinese = /[\u4e00-\u9fa5]/.test(info.msg);
        const isBracketedFormat = /^\[[^\]]+]$/.test(info.msg);
        //是否启用翻译功能
        if (!this.activeTranslate) {
            this.onAddText(data);
            return;
        }

        // info.msg是否包含中文字符或[xxx]格式，直接映射源文本
        if (!containsChinese || isBracketedFormat) {
            data.translation = info.msg
            this.onAddText(data);
            return;
        }
        if (this.activeTranslate && containsChinese && !isBracketedFormat) {
            const regex = /\[.*\]/;
            // 判断是否需要拆分发送翻译请求
            if (regex.test(info.msg) === false) {
                let uuid = getUuid4Hex();
                let translateRequest = {
                    targetLang: this.targetTranslateLanguage,
                    sourceText: info.msg,
                    uuid: uuid
                };
                this.translationWebSocket.send(JSON.stringify(translateRequest));
                // 将消息放入待翻译队列
                this.pendingTranslations[uuid] = data;
            } else {
                const bracketedPattern = /\[[^\]]+\]/g; // 匹配 [xxx] 的部分
                const parts = info.msg.split(bracketedPattern); // 提取 [xxx] 之外的部分
                const brackets = info.msg.match(bracketedPattern) || []; // 提取所有 [xxx] 部分
                // 创建翻译任务，处理非 [xxxx] 部分
                const translatedParts = [];
                for (let i = 0; i < parts.length; i++) {
                    let part = parts[i];

                    if (/[\u4e00-\u9fa5]/.test(part)) { // 仅当部分包含中文时才翻译
                        let uuid = getUuid4Hex();
                        let translateRequest = {
                            targetLang: this.targetTranslateLanguage,
                            sourceText: part, // 仅发送不包含方括号的部分
                            uuid: uuid
                        };

                        // 发送翻译请求
                        this.translationWebSocket.send(JSON.stringify(translateRequest));


                        const translatedPart = await new Promise(resolve => {
                            const temporaryListener = (event) => {
                                const translatedMsg = JSON.parse(event.data).Translation;
                                this.translationWebSocket.removeEventListener('message', temporaryListener); // 清除临时监听器
                                resolve(translatedMsg); // 返回翻译后的部分
                            };
                            this.translationWebSocket.addEventListener('message', temporaryListener);
                        });

                        translatedParts.push(translatedPart); // 将翻译后的部分加入结果
                    } else {
                        translatedParts.push(part); // 无需翻译的部分直接加入结果
                    }

                    // 保持方括号部分原样
                    if (i < brackets.length) {
                        translatedParts.push(brackets[i]);
                    }
                }

                // 拼接翻译后的结果
                data.translation = translatedParts.join('');
                this.onAddText(data); // 确保此处是传递更新后的 data
            }
        }
    }

    sendGiftCallback(command) {
        if (!this.onAddGift) {
            return
        }
        let data = command.data
        let totalCoin;
        if (data.paid !== true) { // 银瓜子礼物
            totalCoin = 0
        } else {
            totalCoin = data.price * data.gift_num
        }

        data = {
            id: getUuid4Hex(),
            avatarUrl: data.uface,
            timestamp: data.timestamp,
            authorName: data.uname,
            totalCoin: totalCoin,
            giftName: data.gift_name,
            num: data.gift_num
        }
        this.onAddGift(data)
    }

    async guardBuyCallback(command) {
        if (!this.onAddMember) {
            return
        }

        let data = command.data
        data = {
            id: getUuid4Hex(),
            avatarUrl: data.user_info.uface,
            timestamp: data.timestamp,
            authorName: data.user_info.uname,
            privilegeType: data.guard_level
        }
        this.onAddMember(data)
    }

    superChatMessageCallback(command) {
        if (!this.onAddSuperChat) {
            return
        }

        let data = command.data
        data = {
            id: data.msg_id.toString(),
            avatarUrl: data.uface,
            timestamp: data.start_time,
            authorName: data.uname,
            price: data.rmb,
            content: data.message,
            translation: '',
            source: 'superChat'
        }
        const containsChinese = /[\u4e00-\u9fa5]/.test(data.content);
        if (this.activeTranslate && this.activeScTranslate && containsChinese) {
            let uuid = getUuid4Hex();
            let translateRequest = {
                targetLang: this.targetTranslateLanguage,
                sourceText: data.content,
                uuid: uuid
            };
            this.translationWebSocket.send(JSON.stringify(translateRequest));
            // 将消息放入待翻译队列
            this.pendingTranslations[uuid] = data;
        } else {
            this.onAddSuperChat(data)
        }
    }

    superChatMessageDeleteCallback(command) {
        if (!this.onDelSuperChat) {
            return
        }

        let ids = []
        for (let id of command.data.message_ids) {
            ids.push(id.toString())
        }
        this.onDelSuperChat({ids})
    }

    // 点赞信息处理
    likeMessageCallback(command) {
        if (!this.onAddText) {
            return
        }
        let info = command.data
        let fansDetect, medalName, medalLevel
        if (info.fans_medal_wearing_status == true) {
            fansDetect = true
            medalName = info.fans_medal_name
            medalLevel = info.fans_medal_level
        } else {
            fansDetect = false
            medalName = null
            medalLevel = 0
        }
        let likeContent = (info.like_count > 1) ? `点赞了直播间${info.like_count}次` : `点赞了直播间`
        let data = {
            id: getUuid4Hex(),
            avatarUrl: info.uface,
            timestamp: info.timestamp,
            authorName: info.uname,
            likeCount: info.like_count,
            privilegeType: 0,
            medalName: medalName,
            medalLevel: medalLevel,
            isFanGroup: fansDetect,
            translation: '',
            emoticon: null,
            content: likeContent,
            authorType: 0
        }
        this.onAddLike(data)
    }
}

// B站弹幕流传来的标准command，勿动
const CMD_CALLBACK_MAP = {
    LIVE_OPEN_PLATFORM_DM: ChatClientDirect.prototype.danmuMsgCallback,
    LIVE_OPEN_PLATFORM_SEND_GIFT: ChatClientDirect.prototype.sendGiftCallback,
    LIVE_OPEN_PLATFORM_GUARD: ChatClientDirect.prototype.guardBuyCallback,
    LIVE_OPEN_PLATFORM_SUPER_CHAT: ChatClientDirect.prototype.superChatMessageCallback,
    LIVE_OPEN_PLATFORM_SUPER_CHAT_DEL: ChatClientDirect.prototype.superChatMessageDeleteCallback,
    LIVE_OPEN_PLATFORM_LIKE: ChatClientDirect.prototype.likeMessageCallback
}